//
//  QSXMLParser.m
//  testXML
//
//  Created by apple on 2017/9/28.
//  Copyright © 2017年 apple. All rights reserved.
//

#import "QSXMLParser.h"

#import <objc/runtime.h>

@interface QSXMLParser ()<NSXMLParserDelegate>
/** 数据数据 */
@property (nonatomic, strong) NSMutableArray *dataArray;

/** 模型类属性数组 */
@property (nonatomic, strong) NSArray *propertiesArray;
/** 装模型类的标签 */
@property (nonatomic, copy) NSString *keyElement;
/** 模型类 */
@property (nonatomic, strong) Class modelClass;

/** 当前标签的名字 */
@property (strong, nonatomic) NSString *currentTagName;

/** 解析完成的Block */
@property (nonatomic, copy) void (^completeBlock)(NSArray *dataArray);

@end

@implementation QSXMLParser
/**
 *  单例
 */
+ (instancetype)qs_XMLParser
{
    // 初始化单例对象
    static QSXMLParser *instance = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[QSXMLParser alloc] init];
    });
    
    return instance;
}

/**
 *  将XML数据转换为对应的数据模型数组
 */
- (void)qs_parserXMLData:(NSData *)xmlData withObjectClass:(__unsafe_unretained Class)objectClass andKeyElement:(NSString *)keyElement complete:(void (^)(NSArray *dataArray))completeBlock
{
    self.keyElement = keyElement;
    self.modelClass = objectClass;
    self.completeBlock = completeBlock;
    // 获取模型类属性
    self.propertiesArray = [self getPropertiesFromClass:objectClass];
    
    // 创建解析器
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    parser.delegate = self;
    // 开始解析
    [parser parse];
}

/**
 *  将XML文件转换为对应的数据模型数组
 */
- (void)qs_parserXMLFile:(NSString *)fileName flieType:(NSString *)fileType isMainBundleFile:(BOOL)isMainBundleFile withObjectClass:(Class)objectClass andKeyElement:(NSString *)keyElement complete:(void (^)(NSArray *))completeBlock
{
    NSString *path = [NSString string];
    if (isMainBundleFile) {
        path = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
    } else {
        path = fileName;
    }
    
    NSData *data = [NSData dataWithContentsOfFile:path];
    [self qs_parserXMLData:data withObjectClass:objectClass andKeyElement:keyElement complete:^(NSArray *dataArray) {
        completeBlock(dataArray);
    }];
}

#pragma mark - NSXMLParserDelegate
/**
 *  文档开始解析
 */
- (void)parserDidStartDocument:(NSXMLParser *)parser {
    // 此方法只在解析开始时触发一次，因此可在这个方法中初始化解析过程中用到的一些成员变量
    self.dataArray = [NSMutableArray new];
}

/**
 *  文档解析出错
 */
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSAssert(parseError, @"解析出错");
}

/**
 *  检测到开始标签时触发
 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    // elementName正在解析的元素的名字
    _currentTagName = elementName;
    
    // 属性在attributeDict参数中传递过来，它是一个字典类型，其中的键的名字就是属性的名字，值是属性的值
    if ([_currentTagName isEqualToString:self.keyElement]) {
        // 创建模型对象
        id model = [self.modelClass new];
        
        // 获取attributeDict中的所有key
        NSArray *keyArray = [attributeDict allKeys];
        // 与属性数组进行比较
        for (NSString *key in keyArray) {
            NSString *value = [attributeDict objectForKey:key];
            
            for (NSString *property in self.propertiesArray) {
                if ([key isEqualToString:property]) {
                    [model setValue:value forKey:property];
                    
                    break;
                }
            }
        }
        
        [self.dataArray addObject:model];
    }
}

/**
 *  遇到字符串时候触发，该方法是解析元素文本内容主要场所
 */
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    // 剔除回车和空格
    // stringByTrimmingCharactersInSet：方法是剔除字符方法
    // [NSCharacterSet whitespaceAndNewlineCharacterSet]指定字符集为换行符和回车符
    string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    if ([string isEqualToString:@""]) {
        return;
    }
    
    id model = [self.dataArray lastObject];
    
    for (NSString *property in self.propertiesArray) {
        if ([_currentTagName isEqualToString:property]) {
            [model setValue:string forKey:property];
            
            break;
        }
    }
}

/**
 *  遇到结束标签时触发，在该方法中主要是清理刚刚解析完成的元素产生的影响，以便于不影响接下来的解析
 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    // 清理刚才解析的元素的名字，以便于记录接下来解析的元素的名字
    self.currentTagName = nil;
}

/**
 *  文档结束解析
 */
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    if (self.completeBlock) {
        self.completeBlock(self.dataArray);
    }
}

#pragma mark - private
/**
 *  获取数据模型中的属性名
 */
- (NSArray *)getPropertiesFromClass:(Class)cls
{
    // 用于保存所有的属性名字
    NSMutableArray *array = [NSMutableArray array];
    
    // 属性个数
    unsigned int outCount;
    // 获取所有的属性    copy,retain,create都需要手动释放
    objc_property_t *properties = class_copyPropertyList(cls, &outCount);
    
    for (int i = 0; i < outCount; i++)
    {
        // 每个属性的结构体
        objc_property_t property = properties[i];
        // 获取属性名字
        const char *name = property_getName(property);
        
        [array addObject:[NSString stringWithUTF8String:name]];
    }
    
    // 释放资源
    free(properties);
    
    return array;
}

#pragma mark - 懒加载
- (NSMutableArray *)dataArray
{
    if (!_dataArray) {
        _dataArray = [NSMutableArray array];
    }
    return _dataArray;
}
@end
